﻿using Mono.CompilerServices.SymbolWriter;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

using SecurityType = System.Collections.Generic.Dictionary<System.Security.Permissions.SecurityAction, System.Security.PermissionSet>;
using Mono.Security.Cryptography;
using System.Security.Cryptography;
using System.Globalization;
using System.IO;

namespace Teal.Compiler {

    /// <summary>
    /// 表示一个包。
    /// </summary>
    public partial class PackageDefinition {

        /// <summary>
        /// 获取当前包的主模块。
        /// </summary>
        public ModuleDefinition mainModule = new ModuleDefinition();

        /// <summary>
        /// 获取当前产生的错误数。
        /// </summary>
        public int errorCount;

        /// <summary>
        /// 获取当前产生的警告数。
        /// </summary>
        public int warningCount;

        /// <summary>
        /// 设置当前包的入口点。
        /// </summary>
        private void setEntryPoint() {

            if (Compiler.options.targetType == CompileTargetType.lib) {
                if (Compiler.options.mainClass != null) {
                    Compiler.error(ErrorCode.none, "在编译类库时无法指定 main 函数");
                }
                return;
            }

            PEFileKinds file_kind;

            switch (Compiler.options.targetType) {
                case CompileTargetType.win32Lib:
                    file_kind = PEFileKinds.Dll;
                    break;
                case CompileTargetType.win32App:
                    file_kind = PEFileKinds.WindowApplication;
                    break;
                default:
                    file_kind = PEFileKinds.ConsoleApplication;
                    break;
            }

            if (entry_point == null) {
                string main_class = Compiler.options.mainClass;
                if (main_class != null) {
                    // TODO: Handle dotted names
                    //var texpr = mainModule.GlobalRootNamespace.LookupType(mainModule, main_class, 0, LookupMode.Probing, Location2.Null);
                    //if (texpr == null) {
                    //    Compiler.Report.Error(1555, "Could not find `{0}' specified for Main method", main_class);
                    //    return;
                    //}

                    //TypeDefinition mtype = (TypeDefinition)(texpr.MemberDefinition2 as ClassDefinition) ?? (TypeDefinition)(texpr.MemberDefinition2 as StructDefinition);
                    //if (mtype == null) {
                    //    Compiler.Report.Error(1556, "`{0}' specified for Main method must be a valid class or struct", main_class);
                    //    return;
                    //}

                    //Compiler.Report.Error(1558, mtype.startLocation2, "`{0}' does not have a suitable static Main method", mtype.GetSignatureForError());
                } else {
                    string pname = Compiler.options.outputPath == null ? name : Path.GetFileName(Compiler.options.outputPath);
                    Compiler.Report.Error(5001, "Program `{0}' does not contain a static `Main' method suitable for an entry point",
                        pname);
                }

                return;
            }

            builder.SetEntryPoint(entry_point.MethodBuilder, file_kind);
        }

        /// <summary>
        /// 根据当前配置对整个包编译。
        /// </summary>
        public void compile() {

            // 语法解析。
            parse();
            if (Compiler.errorCount != 0) {
                return;
            }

            // 语义分析。
            resolve();
            if (Compiler.errorCount != 0) {
                return;
            }

            emit();

            save();

            if (Compiler.options.docOutputPath != null) {
                // todo:  try
                using (var writer = new System.CodeDom.Compiler.IndentedTextWriter(new StreamWriter(Compiler.options.docOutputPath, false, Compiler.options.docEncoding ?? Compiler.options.sourceEncoding ?? Encoding.UTF8))) {
                    mainModule.generateDoc(writer);
                }
            }

        }

        /// <summary>
        /// 对当前所有输入文件进行语法解析。
        /// </summary>
        public void parse() {
            var parser = new Parser();
            MemberDefinition last = null;
            foreach (string sourceFile in Compiler.options.sourceFiles) {
                var current = parser.parseFile(sourceFile, Compiler.options.sourceEncoding);
                if (last == null) {
                    last = mainModule.members = current;
                } else {
                    last = last.next = current;
                }
            }
        }

        /// <summary>
        /// 对所有语法树进行语义分析。
        /// </summary>
        public void resolve() {
            mainModule.resolve();
        }

        /// <summary>
        /// 对所有代码进行生成。
        /// </summary>
        public void emit() {
            emit(AppDomain.CurrentDomain, Path.GetFileName(Compiler.options.outputPath), AssemblyBuilderAccess.Save);
        }

        /// <summary>
        /// 保存当前模块的代码。
        /// </summary>
        public void save() {
            PortableExecutableKinds pekind = PortableExecutableKinds.ILOnly;
            ImageFileMachine machine;

            switch (Compiler.options.targetPlatform) {
                case CompileTargetPlatform.x86:
                    pekind |= PortableExecutableKinds.Required32Bit;
                    machine = ImageFileMachine.I386;
                    break;
                case CompileTargetPlatform.x64:
                    pekind |= PortableExecutableKinds.PE32Plus;
                    machine = ImageFileMachine.AMD64;
                    break;
                case CompileTargetPlatform.ia64:
                    machine = ImageFileMachine.IA64;
                    break;
                case CompileTargetPlatform.all32Preferred:
                    throw new NotSupportedException();
                case CompileTargetPlatform.arm:
                    throw new NotSupportedException();
                case CompileTargetPlatform.all:
                default:
                    machine = ImageFileMachine.I386;
                    break;
            }

            //try {
                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    SaveModule(pekind, machine);
                } else {
                    builder.Save(mainModule.Builder.ScopeName, pekind, machine);
                }
            //} catch (Exception e) {
            //    Compiler.Report.Error(16, "Could not write to file `" + name + "', cause: " + e.Message);
            //}


            // Save debug symbols file
            if (symbol_writer != null && Teal.Compiler.Compiler.errorCount == 0) {
                // TODO: it should run in parallel


                var filename = Compiler.options.outputPath + ".mdb";
                try {
                    // We mmap the file, so unlink the previous version since it may be in use
                    File.Delete(filename);
                } catch {
                    // We can safely ignore
                }

                mainModule.writeDebugSymbol(symbol_writer);

                using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write)) {
                    symbol_writer.CreateSymbolFile(mainModule.Builder.ModuleVersionId, fs);
                }

            }
        }

        ///// <summary>
        ///// 设置当前程序的入口点。
        ///// </summary>
        //public void setEntryPoint() {
        //    MethodReference entryPoint;


        //}

        //private MethodReference findEntryPoint() {
            
        //}

        #region 暂时无法删除

        MethodDefinition entry_point;

        //
        // Assembly entry point, aka Main method
        //
        public MethodDefinition EntryPoint {
            get {
                return entry_point;
            }
            set {
                entry_point = value;
            }
        }


        public PackageDefinition() { }

        void SetCustomAttribute(MethodReference ctor, byte[] data) {
            if (module_target_attrs != null)
                module_target_attrs.AddAssemblyAttribute(ctor, data);
            else
                builder.SetCustomAttribute((ConstructorInfo)ctor.GetMetaInfo(), data);
        }

        void Error_AssemblySigning(string text) {
            Compiler.Report.Error(1548, "Error during assembly signing. " + text);
        }

        static Version IsValidAssemblyVersion(string version, bool allowGenerated) {
            string[] parts = version.Split('.');
            if (parts.Length < 1 || parts.Length > 4)
                return null;

            var values = new int[4];
            for (int i = 0; i < parts.Length; ++i) {
                if (!int.TryParse(parts[i], out values[i])) {
                    if (parts[i].Length == 1 && parts[i][0] == '*' && allowGenerated) {
                        if (i == 2) {
                            // Nothing can follow *
                            if (parts.Length > 3)
                                return null;

                            // Generate Build value based on days since 1/1/2000
                            TimeSpan days = DateTime.Today - new DateTime(2000, 1, 1);
                            values[i] = System.Math.Max(days.Days, 0);
                            i = 3;
                        }

                        if (i == 3) {
                            // Generate Revision value based on every other second today
                            var seconds = DateTime.Now - DateTime.Today;
                            values[i] = (int)seconds.TotalSeconds / 2;
                            continue;
                        }
                    }

                    return null;
                }

                if (values[i] > ushort.MaxValue)
                    return null;
            }

            return new Version(values[0], values[1], values[2], values[3]);
        }
        #region Properties

        public string FullName {
            get {
                return builder.FullName;
            }
        }

        // TODO: This should not exist here but will require more changes
        public MetadataImporter Importer {
            get;
            set;
        }

        public bool IsMissing {
            get {
                return false;
            }
        }

        public bool IsSatelliteAssembly { get; private set; }

        public string Name {
            get {
                return name;
            }
        }

        public bool WrapNonExceptionThrows {
            get {
                return wrap_non_exception_throws;
            }
        }

        public MonoSymbolFile SymbolWriter {
            get {
                return symbol_writer;
            }
        }

        #endregion

        public void ApplyAttributeBuilder(MemberDefinition.MemberAnnotation a, MethodReference ctor, byte[] cdata, PredefinedAttributes pa) {
            if (a.IsValidSecurityAttribute()) {
                a.ExtractSecurityPermissionSet(ctor, ref declarative_security);
                return;
            }

            if (a.Type == pa.AssemblyCulture) {
                string value = a.GetString();
                if (value == null || value.Length == 0)
                    return;

                if (Compiler.options.targetType == CompileTargetType.win32Console) {
                    Compiler.Report.Error(7059, a.Location, "Executables cannot be satellite assemblies. Remove the attribute or keep it empty");
                    return;
                }

                if (value == "neutral")
                    value = "";

                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    SetCustomAttribute(ctor, cdata);
                } else {
                    builder_extra.SetCulture(value, a.Location);
                }

                IsSatelliteAssembly = true;
                return;
            }

            if (a.Type == pa.AssemblyVersion) {
                string value = a.GetString();
                if (value == null || value.Length == 0)
                    return;

                var vinfo = IsValidAssemblyVersion(value, true);
                if (vinfo == null) {
                    Compiler.Report.Error(7034, a.Location, "The specified version string `{0}' does not conform to the required format - major[.minor[.build[.revision]]]",
                        value);
                    return;
                }

                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    SetCustomAttribute(ctor, cdata);
                } else {
                    builder_extra.SetVersion(vinfo, a.Location);
                }

                return;
            }

            if (a.Type == pa.AssemblyAlgorithmId) {
                const int pos = 2; // skip CA header
                uint alg = (uint)cdata[pos];
                alg |= ((uint)cdata[pos + 1]) << 8;
                alg |= ((uint)cdata[pos + 2]) << 16;
                alg |= ((uint)cdata[pos + 3]) << 24;

                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    SetCustomAttribute(ctor, cdata);
                } else {
                    builder_extra.SetAlgorithmId(alg, a.Location);
                }

                return;
            }

            if (a.Type == pa.AssemblyFlags) {
                const int pos = 2; // skip CA header
                uint flags = (uint)cdata[pos];
                flags |= ((uint)cdata[pos + 1]) << 8;
                flags |= ((uint)cdata[pos + 2]) << 16;
                flags |= ((uint)cdata[pos + 3]) << 24;

                // Ignore set PublicKey flag if assembly is not strongnamed
                if ((flags & (uint)AssemblyNameFlags.PublicKey) != 0 && public_key == null)
                    flags &= ~(uint)AssemblyNameFlags.PublicKey;

                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    SetCustomAttribute(ctor, cdata);
                } else {
                    builder_extra.SetFlags(flags, a.Location);
                }

                return;
            }

            if (a.Type == pa.TypeForwarder) {
                TypeReference t = a.GetArgumentType();
                if (t == null || TypeManager.HasElementType(t)) {
                    Compiler.Report.Error(735, a.Location, "Invalid type specified as an argument for TypeForwardedTo attribute");
                    return;
                }

                if (emitted_forwarders == null) {
                    emitted_forwarders = new Dictionary<ITypeDefinition, MemberDefinition.MemberAnnotation>();
                } else if (emitted_forwarders.ContainsKey(t.MemberDefinition2)) {
                    Compiler.Report.SymbolRelatedToPreviousError(emitted_forwarders[t.MemberDefinition2].Location, null);
                    Compiler.Report.Error(739, a.Location, "A duplicate type forward of type `{0}'",
                        t.GetSignatureForError());
                    return;
                }

                emitted_forwarders.Add(t.MemberDefinition2, a);

                if (t.MemberDefinition2.DeclaringAssembly == this) {
                    Compiler.Report.SymbolRelatedToPreviousError(t);
                    Compiler.Report.Error(729, a.Location, "Cannot forward type `{0}' because it is defined in this assembly",
                        t.GetSignatureForError());
                    return;
                }

                if (t.IsNested) {
                    Compiler.Report.Error(730, a.Location, "Cannot forward type `{0}' because it is a nested type",
                        t.GetSignatureForError());
                    return;
                }

                builder_extra.AddTypeForwarder(t.GetDefinition(), a.Location);
                return;
            }

            if (a.Type == pa.Extension) {
                a.Error_MisusedExtensionAttribute();
                return;
            }

            if (a.Type == pa.InternalsVisibleTo) {
                string assembly_name = a.GetString();
                if (assembly_name == null) {
                    Compiler.Report.Error(7030, a.Location, "Friend assembly reference cannot have `null' value");
                    return;
                }

                if (assembly_name.Length == 0)
                    return;
#if STATIC
				ParsedAssemblyName aname;
				ParseAssemblyResult r = Fusion.ParseAssemblyName (assembly_name, out aname);
				if (r != ParseAssemblyResult.OK) {
					Report.Warning (1700, 3, a.Location, "Friend assembly reference `{0}' is invalid and cannot be resolved",
						assembly_name);
					return;
				}

				if (aname.Version != null || aname.Culture != null || aname.ProcessorArchitecture != ProcessorArchitecture.None) {
					Report.Error (1725, a.Location,
						"Friend assembly reference `{0}' is invalid. InternalsVisibleTo declarations cannot have a version, culture or processor architecture specified",
						assembly_name);

					return;
				}

				if (public_key != null && !aname.HasPublicKey) {
					Report.Error (1726, a.Location,
						"Friend assembly reference `{0}' is invalid. Strong named assemblies must specify a public key in their InternalsVisibleTo declarations",
						assembly_name);
					return;
				}
#endif
            } else if (a.Type == pa.RuntimeCompatibility) {
                wrap_non_exception_throws_custom = true;
            } else if (a.Type == pa.AssemblyFileVersion) {
                vi_product_version = a.GetString();
                if (string.IsNullOrEmpty(vi_product_version) || IsValidAssemblyVersion(vi_product_version, false) == null) {
                    Compiler.Report.Warning(7035, 1, a.Location, "The specified version string `{0}' does not conform to the recommended format major.minor.build.revision",
                        vi_product_version, a.Name);
                    return;
                }

                // File version info decoding from blob is not supported
                var cab = new CustomAttributeBuilder((ConstructorInfo)ctor.GetMetaInfo(), new object[] { vi_product_version });
                builder.SetCustomAttribute(cab);
                return;
            } else if (a.Type == pa.AssemblyProduct) {
                vi_product = a.GetString();
            } else if (a.Type == pa.AssemblyCompany) {
                vi_company = a.GetString();
            } else if (a.Type == pa.AssemblyDescription) {
                // TODO: Needs extra api
            } else if (a.Type == pa.AssemblyCopyright) {
                vi_copyright = a.GetString();
            } else if (a.Type == pa.AssemblyTrademark) {
                vi_trademark = a.GetString();
            } else if (a.Type == pa.Debuggable) {
                has_user_debuggable = true;
            }

            SetCustomAttribute(ctor, cdata);
        }

        protected AssemblyName CreateAssemblyName() {
            var an = new AssemblyName(name);
            return an;
        }

        // TODO: make it private and move all builder based methods here
        
        protected AssemblyBuilderExtension builder_extra;
        MonoSymbolFile symbol_writer;

        bool wrap_non_exception_throws;
        bool wrap_non_exception_throws_custom;
        bool has_user_debuggable;

         string name {
            get {
                return Path.GetFileNameWithoutExtension(Compiler.options.outputPath);
            }
        }

        byte[] public_key, public_key_token;
       
        // Holds private/public key pair when private key
        // was available
        StrongNameKeyPair private_key;

        SecurityType declarative_security;
        Dictionary<ITypeDefinition, MemberDefinition.MemberAnnotation> emitted_forwarders;
        AssemblyAttributesPlaceholder module_target_attrs;

        // Win32 version info values
        string vi_product, vi_product_version, vi_company, vi_copyright, vi_trademark;

        //
        // In-memory only assembly container
        //
        public PackageDefinition(ModuleDefinition module) {
            this.mainModule = module;

            wrap_non_exception_throws = true;

        }

        /// <summary>
        /// 获取当前包对应的构建器。
        /// </summary>
        public AssemblyBuilder builder;

        /// <summary>
        /// 生成当前模块的代码。
        /// </summary>
        public void emit(AppDomain domain, string name, AssemblyBuilderAccess access) {

            // 创建模块构建器。
            builder = domain.DefineDynamicAssembly(new AssemblyName(name), access, Path.GetDirectoryName(Compiler.options.outputPath));

            builder_extra = new AssemblyBuilderMonoSpecific(builder);

            // 设置调试文档。
            if (Compiler.options.debugOutputPath != null) {
                symbol_writer = new MonoSymbolFile();
            }

            mainModule.emit();

        }

        public void Emit() {
            //if (Compiler.options.targetType == CompileTargetType.win32Lib) {
            //    module_target_attrs = new AssemblyAttributesPlaceholder(mainModule, name);
            //   // module_target_attrs.CreateContainer();
            //    module_target_attrs.resolveBaseTypes();
            //    module_target_attrs.Define();
            //    mainModule.AddCompilerGeneratedClass(module_target_attrs);
            //}

            mainModule.EmitContainer();

            if (!IsSatelliteAssembly) {
                if (!has_user_debuggable && Compiler.options.debugOutputPath != null) {
                    var pa = mainModule.PredefinedAttributes.Debuggable;
                    if (pa.IsDefined) {
                        var modes = System.Diagnostics.DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints;
                        if (Compiler.options.optimize == OptimizeLevel.none)
                            modes |= System.Diagnostics.DebuggableAttribute.DebuggingModes.DisableOptimizations;

                        pa.EmitAttribute(builder, modes);
                    }
                }

                if (!wrap_non_exception_throws_custom) {
                    PredefinedAttribute pa = mainModule.PredefinedAttributes.RuntimeCompatibility;
                    if (pa.IsDefined && pa.ResolveBuilder()) {
                        var prop = mainModule.PredefinedMembers.RuntimeCompatibilityWrapNonExceptionThrows.Get();
                        if (prop != null) {
                            AttributeEncoder encoder = new AttributeEncoder();
                            encoder.EncodeNamedPropertyArgument(prop, new BoolLiteral(Compiler.BuiltinTypes, true, Location2.Null));
                            SetCustomAttribute(pa.Constructor, encoder.ToArray());
                        }
                    }
                }

            }

            setEntryPoint();
        }

        public void Resolve() {
            if (!Compiler.options.disallowUnsafeCode && mainModule.PredefinedTypes.SecurityAction.Define()) {
                //
                // Emits [assembly: SecurityPermissionAttribute (SecurityAction.RequestMinimum, SkipVerification = true)]
                // when -unsafe option was specified
                //
                Location2 loc = Location2.Null;

                MemberCallExpression system_security_permissions = new MemberCallExpression(new MemberCallExpression(
                    new QualifiedAliasMember(QualifiedAliasMember.GlobalAlias, "System", loc), "Security", loc), "Permissions", loc);

                var req_min = mainModule.PredefinedMembers.SecurityActionRequestMinimum.Resolve(loc);

                Arguments pos = new Arguments(1);
                pos.Add(new Argument(req_min.GetConstant(null)));

                Arguments named = new Arguments(1);
                named.Add(new NamedArgument("SkipVerification", loc, new BoolLiteral(Compiler.BuiltinTypes, true, loc)));

                MemberDefinition.MemberAnnotation g = new MemberDefinition.MemberAnnotation();

                // Disable no-location warnings (e.g. obsolete) for compiler generated attribute
                Compiler.Report.DisableReporting();
                try {
                    var ctor = g.Resolve();
                    if (ctor != null) {
                        g.ExtractSecurityPermissionSet(ctor, ref declarative_security);
                    }
                } finally {
                    Compiler.Report.EnableReporting();
                }
            }

        }

        public void EmbedResources() {
            //
            // Add Win32 resources
            //
            if (Compiler.options.win32Resource != null) {
                builder.DefineUnmanagedResource(Compiler.options.win32Resource);
            } else {
                builder.DefineVersionInfoResource(vi_product, vi_product_version, vi_company, vi_copyright, vi_trademark);
            }

            if (Compiler.options.win32Icon != null) {
                builder_extra.DefineWin32IconResource(Compiler.options.win32Icon);
            }

            if (Compiler.options.embedResources != null) {
                if (Compiler.options.targetType == CompileTargetType.win32Lib) {
                    Compiler.Report.Error(1507, "Cannot link resource file when building a module");
                } else {
                    int counter = 0;
                    foreach (var res in Compiler.options.embedResources) {
                        if (!File.Exists(res.Value)) {
                            Compiler.Report.Error(1566, "Error reading resource file `{0}'", res.Value);
                            continue;
                        }

                        //if (res.IsEmbeded) {
                        Stream stream;
                        if (counter++ < 10) {
                            stream = File.OpenRead(res.Value);
                        } else {
                            // TODO: SRE API requires resource stream to be available during AssemblyBuilder::Save
                            // we workaround it by reading everything into memory to compile projects with
                            // many embedded resource (over 3500) references
                            stream = new MemoryStream(File.ReadAllBytes(res.Value));
                        }

                        mainModule.Builder.DefineManifestResource(res.Key, stream, ResourceAttributes.Public);
                        //} else {
                        //    Builder.AddResourceFile (res.Name, Path.GetFileName (res.FileName), res.Attributes);
                        //}
                    }
                }
            }
        }

        protected void SaveModule(PortableExecutableKinds pekind, ImageFileMachine machine) {
            try {
                var module_only = typeof(AssemblyBuilder).GetProperty("IsModuleOnly", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                var set_module_only = module_only.GetSetMethod(true);

                set_module_only.Invoke(builder, new object[] { true });
            } catch {
                Compiler.Report.RuntimeMissingSupport(Location2.Null, "-target:module");
            }

            builder.Save(Compiler.options.outputPath, pekind, machine);
        }




        #endregion

    }

}
